feat: HTML widget support (MCP Apps UI)#626
Draft
corinagum wants to merge 12 commits into
Draft
Conversation
Add types for the HTML widget contract: - IHtmlWidgetPayload, IHtmlWidgetSecurityPolicy, IHtmlWidgetPermissions - ICallToolRequest, IMcpUiCallToolResult - IHtmlWidgetCallToolInvokeActivity (htmlwidget/calltool invoke) - Add 'extendedmarkdown' to TextFormat union - Add htmlwidget/calltool to InvokeResponseBody map Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add 'htmlwidget/calltool' -> 'widget.callTool' invoke alias - Add WidgetCallToolRoutes type for typed handler registration - Add buildHtmlWidgetMarkdown() and buildHtmlWidgetMessage() helpers - Add unit tests (14 cases including edge cases) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Adds first-class “HTML widget” (MCP Apps UI) support to the Teams TypeScript SDK by introducing new API models for widget payloads + callTool invoke wiring, app-side helpers for building/injecting widget protocol HTML, and accompanying examples + tests.
Changes:
- Added
@microsoft/teams.apimodels for HTML widget payloads/security policy and thehtmlwidget/calltoolinvoke request/response contract. - Added
@microsoft/teams.appshelpers to build widget markdown/message activities and to optionally inject the MCP Apps protocol + validate security policy references. - Added an
examples/html-widgetssample bot and integration/unit tests covering send/update/delete and policy validation.
Reviewed changes
Copilot reviewed 30 out of 31 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| test/integration/src/html-widgets.test.ts | New integration tests for sending/updating/deleting widget messages using extendedmarkdown. |
| test/integration/src/fixture.ts | Makes meetingId optional and stops requiring TEST_MEETING_ID for integration test config. |
| test/integration/jest.config.ts | Increases integration test timeout for slower widget/canary operations. |
| packages/apps/src/utils/html-widget.ts | New widget helpers: payload validation, protocol injection, markdown/message builders, security policy static validation. |
| packages/apps/src/utils/html-widget.spec.ts | New unit tests validating protocol injection, markdown formatting, payload validation, and security policy warnings. |
| packages/apps/src/routes/invoke/widget-calltool.ts | Adds typed route surface for widget.callTool. |
| packages/apps/src/routes/invoke/index.ts | Adds invoke alias mapping htmlwidget/calltool -> widget.callTool and exports the new route type. |
| packages/apps/src/index.ts | Re-exports the new HTML widget utilities from @microsoft/teams.apps. |
| packages/api/src/models/invoke-response.ts | Adds htmlwidget/calltool invoke response body typing. |
| packages/api/src/models/index.ts | Exports new html-widget models. |
| packages/api/src/models/html-widget/index.ts | Barrel export for widget model types. |
| packages/api/src/models/html-widget/html-widget-payload.ts | Defines widget payload, permissions, and security policy types. |
| packages/api/src/models/html-widget/call-tool-result.ts | Defines MCP UI call tool result + Teams wire wrapper response type. |
| packages/api/src/models/html-widget/call-tool-request.ts | Defines htmlwidget/calltool invoke request payload. |
| packages/api/src/activities/invoke/index.ts | Extends InvokeActivity union to include widget callTool invoke. |
| packages/api/src/activities/invoke/html-widget/index.ts | Barrel export for widget invoke activity types. |
| packages/api/src/activities/invoke/html-widget/call-tool.ts | Defines the htmlwidget/calltool invoke activity shape. |
| package-lock.json | Adds the new example workspace entry and updates lockfile dependencies. |
| examples/html-widgets/tsconfig.json | TypeScript config for the new example workspace. |
| examples/html-widgets/src/widgets/update-context.ts | Example widget exercising ui/update-model-context. |
| examples/html-widgets/src/widgets/simple.ts | Minimal static widget HTML example. |
| examples/html-widgets/src/widgets/open-link.ts | Example widget exercising ui/open-link. |
| examples/html-widgets/src/widgets/multi-tool.ts | Example widget that calls multiple tools (tools/call) for dispatch testing. |
| examples/html-widgets/src/widgets/messageback.ts | Example widget exercising ui/message (messageBack-like behavior). |
| examples/html-widgets/src/widgets/host-context.ts | Example widget showing hostContext + listening for host-context-changed notifications. |
| examples/html-widgets/src/widgets/fullscreen.ts | Example widget requesting fullscreen display mode. |
| examples/html-widgets/src/widgets/calltool.ts | Example widget calling a refresh tool and rendering results. |
| examples/html-widgets/src/index.ts | Example bot wiring commands + typed widget.callTool handler returning the wrapper response. |
| examples/html-widgets/README.md | Example documentation and architecture overview for widget contract. |
| examples/html-widgets/package.json | New example workspace package definition and scripts. |
| examples/html-widgets/eslint.config.js | ESLint config wiring for the new example workspace. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+25
to
+27
| if (!payload.domain?.trim() || !payload.domain.startsWith('https://')) { | ||
| throw new Error('HTML widget payload requires "domain" to be a valid URL starting with "https://".'); | ||
| } |
| * @experimental This API is in preview and may change in the future. | ||
| * Diagnostic: ExperimentalTeamsHtmlWidget | ||
| */ | ||
| function validateHtmlWidgetPayload(payload: IHtmlWidgetPayload): void { |
| export { toThreadedConversationId } from './utils/thread'; | ||
|
|
||
| // HTML Widget utilities | ||
| export { buildHtmlWidgetMarkdown, buildHtmlWidgetMessage, injectWidgetProtocol, validateSecurityPolicy } from './utils/html-widget'; |
Comment on lines
+31
to
+32
| Bot returns: | ||
| { status: 200, body: { content: [...], structuredContent: {...}, isError: false } } |
| + 'if(d.id===id&&d.result){window.parent.postMessage({jsonrpc:\'2.0\',method:\'ui/notifications/initialized\'},\'*\');setTimeout(notifySize,100);}' | ||
| + hookLines | ||
| + '});' | ||
| + `window.parent.postMessage({jsonrpc:'2.0',id:id,method:'ui/initialize',params:{protocolVersion:'${MCP_PROTOCOL_VERSION}',appInfo:{name:'${name}',version:'${version}'},appCapabilities:${capsJson}}},'*');` |
| // connectDomains: fetch(), XMLHttpRequest.open(), new WebSocket(), new EventSource() | ||
| const connectPatterns: Array<{ regex: RegExp; source: string }> = [ | ||
| { regex: /fetch\(\s*["']([^"']+)["']/gi, source: 'fetch()' }, | ||
| { regex: /\.open\(\s*["'][A-Z]+["']\s*,\s*["']([^"']+)["']/gi, source: 'XMLHttpRequest.open()' }, |
| test target. It verifies that: | ||
| - The SDK's invoke route alias correctly matches `htmlwidget/calltool` | ||
| - The typed handler receives `{ name, arguments }` in `activity.value` | ||
| - The response body format (`McpUiCallToolResult`) is accepted by the client |
| const iframeTagRegex = /<iframe\s[^>]*>/gi; | ||
| const iframeSrcRegex = /src=["']([^"']+)["']/i; | ||
| let tagMatch; | ||
| while ((tagMatch = iframeTagRegex.exec(html)) !== null) { |
| // connectDomains: <form action> (form submissions can exfiltrate data) | ||
| const formTagRegex = /<form\s[^>]*>/gi; | ||
| const formActionRegex = /action=["']([^"']+)["']/i; | ||
| while ((tagMatch = formTagRegex.exec(html)) !== null) { |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds HTML widget support to the Teams TypeScript SDK, enabling bots to send rich interactive MCP Apps UI widgets in Teams messages.
What's included
API types (
@microsoft/teams.api)IHtmlWidgetPayload,IHtmlWidgetSecurityPolicy,IHtmlWidgetPermissionsIMcpUiCallToolResult,IHtmlWidgetCallToolResponse(wire-format wrapper)IHtmlWidgetCallToolInvokeActivityand invoke response mappingextendedmarkdownadded toTextFormatApp helpers (
@microsoft/teams.apps)buildHtmlWidgetMarkdown- wraps payload in```html-widgetcode fencebuildHtmlWidgetMessage- builds a ready-to-send message activityinjectWidgetProtocol- optional convenience helper that injects the MCP Apps protocol (ui/initialize handshake, size reporting, notification hooks)validateHtmlWidgetPayload- runtime validation (name, html, domain)widget.callToolinvoke routeExample bot (
examples/html-widgets/)widget.callToolhandler with multi-tool dispatchIntegration tests
Preview markers
@experimentalwithDiagnostic: ExperimentalTeamsHtmlWidgetDesign decisions
injectWidgetProtocolis called internally by the builders, but widgets that implement the MCP Apps protocol themselves are returned unchanged (detected by presence ofui/initialize)Related
Status
Draft - pending: